#pragma once
#include<iostream>
#include<pthread.h>
#include<time.h>
#include<unistd.h>
#include<queue>

using namespace std;

int gDefaultCap = 5;//默认的阻塞队列容量上限

template <class T> // 模板类型
class BlockQueue
{
private:
    bool isQueueFull()
    {
        return _bq.size() == _capacity;
    }

    bool isQueueEmpty()
    {
        return _bq.size() == 0;
    }

public:
    //构造函数：初始化容量上限、初始化互斥锁、初始化条件变量
    BlockQueue(int capacity = gDefaultCap)
        : _capacity(capacity)//容量上限：由用户决定需要多大容量
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_Empty, nullptr);
        pthread_cond_init(&_Full, nullptr);
    }

    //析构函数：销毁互斥锁、销毁条件变量
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_Empty);
        pthread_cond_destroy(&_Full);
    }

    //Push:放数据的接口：由生产者线程不断向阻塞队列中放数据
    void Push(const T& in)   
    {
        //临界资源区：先上锁
        pthread_mutex_lock(&_mutex);
        //判断当前临界资源是否满足条件：对生产者、isQueueFull
        while(isQueueFull()) pthread_cond_wait(&_Full, &_mutex);
        //代码执行到此步：当前生产者线程持有锁，且阻塞队列未满--->可放入数据
        _bq.push(in);
        //完成后，解锁，并唤醒消费者线程（告诉它此时交易场所中有资源，可以取用）
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_Empty);
    }

    //Pop:取数据的接口：由消费者线程不断从阻塞队列中取数据
    void Pop(T* out)
    {
        //临界资源区：先上锁
        pthread_mutex_lock(&_mutex);
        //判断当前临界资源是否满足条件：对消费者、isQueueEmpty
        while(isQueueEmpty()) pthread_cond_wait(&_Empty, &_mutex);
        //代码执行到此步：当前消费者线程持有锁，且阻塞队列不空--->可取到数据
        *out = _bq.front();
        _bq.pop();
        //完成后，解锁，并唤醒生产者线程（告诉它此时交易场所中资源被取出，可补充）
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_Full);
    }

private:
    queue<T> _bq;           // 使用库里的队列做阻塞队列
    int _capacity;          // 阻塞队列的容量上限
    pthread_mutex_t _mutex; // 互斥锁：保证队列安全
    pthread_cond_t _Empty;  // 判断阻塞队列为空的条件变量：关心该条件的是消费者（空了就不能取数据）
    pthread_cond_t _Full;   // 判断阻塞队列为满的条件变量：关心该条件的是生产者（满了就不能放数据）

    // PS：一些解释
    /*
        生产者消费者模型中，阻塞队列充当了交易场所的角色，生产者消费者线程能同时访问该交易场所，即访问了临界资源。
        1、多线程同时Push/pop：因此需要互斥，保证临界资源一次只能让一线程操作；
        2、在Push的同时存在Pop，因此需要同步，保证访问临界资源的顺序性。
    */
};
